package com.stone.generator.adapter;

import com.google.common.base.Charsets;
import com.stone.generator.pojo.info.BaseInfo;
import com.stone.generator.pojo.info.GeneratorInfo;
import com.stone.generator.pojo.info.ProjectInfo;
import com.stone.generator.util.CamelUtil;
import com.stone.generator.util.IMap;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.util.StringUtils;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * author : WH
 * time : 2018/11/13 4:40 PM
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ProjectGenerator extends AbstractProjectGenerator {

    private static final long serialVersionUID = 3602863688155921196L;

    public static final String ASSEMBLY = "assembly";

    public static final String PERSIST = "dal";

    public static final String FACADE = "facade";

    public static final String SERVICE = "service";

    public static final String POJO = "pojo";

    private static final String[] ARRAY_MODULE = new String[]{"adapter", ASSEMBLY, "client", "common", FACADE, PERSIST, "web"};

    private static final String[] ARRAY_PROFILE = new String[]{"local", "dev", "test", "online"};

    private static final String[] ARRAY_DIRECT = new String[]{"main", "test"};

    private static final String PREFIX = "templates/project/";

    private static final String PREFIX_CONFIG = PREFIX + ASSEMBLY + "/";

    private static final String PREFIX_MYBATIS = "templates/project/" + PERSIST + "/";

    private static final String PREFIX_SERVICE = PREFIX + FACADE + "/" + SERVICE + "/";

    private static final String PREFIX_POJO = PREFIX + FACADE + "/" + POJO + "/" + "base" + "/";

    /**
     * 可以配置模板
     */
    private String[] arrayModule;
    /**
     * 可以配置环境
     */
    private String[] arrayProfile;
    /**
     * 可以配置模板前缀
     */
    private String prefix;
    /**
     * 可以配置配置文件前缀
     */
    private String prefixConfig;
    /**
     * Mybatis-config前缀
     */
    private String prefixMybatis;


    public ProjectGenerator() {
        this.prefix = PREFIX;
        this.arrayModule = ARRAY_MODULE;
        this.arrayProfile = ARRAY_PROFILE;
        this.prefixConfig = PREFIX_CONFIG;
        this.prefixMybatis = PREFIX_MYBATIS;
    }

    @Override
    public List<String> initTemplate() {
        List<String> list = Arrays.stream(arrayModule).map(e -> prefix + e + XIE + "pom.xml.vm").collect(Collectors.toList());
        list.add(prefix + "pom.xml.vm");
        //新增 Dockerfile
        list.add(prefix + "Dockerfile.vm");
        list.add(prefix + ".gitignore.vm");
        return list;
    }

    @Override
    public List<String> initYmlTemplate() {
        List<String> list = Arrays.stream(arrayProfile).map(e -> prefixConfig + "application" + HENG + e + ".yml.vm").collect(Collectors.toList());
        list.add(prefixConfig + "application.yml.vm");
        list.add(prefixConfig + "application-swagger.yml.vm");
        Arrays.stream(arrayProfile).forEach(e -> list.add(prefixConfig + "log/logback" + HENG + e + ".xml.vm"));
        return list;
    }

    @Override
    public List<String> initDirectTemplate() {
        return Arrays.asList(arrayModule);
    }

    @Override
    public List<String> initApplicationTemplate() {
        return Arrays.asList(PREFIX_CONFIG + "application.java.vm");
    }

    @Override
    public List<String> initMybatisConfigTemplate() {
        return Arrays.asList(prefixMybatis + "mybatis-config.xml.vm", prefixMybatis + "generatorConfig.xml.vm");
    }

    @Override
    public Map<String, List<String>> initModuleConfigTemplate() {
        IMap<String, List<String>> map = new IMap<>();
        String pathConfig = PREFIX_CONFIG + "config/";
        List<String> list = new ArrayList<>(3);
        list.add(PREFIX_CONFIG + "application.java.vm");
        list.add(pathConfig + "executorConfig.java.vm");
        list.add(pathConfig + "globalExceptionHandler.java.vm");
        List<String> listFacade = new ArrayList<>(4);
        listFacade.add(PREFIX_SERVICE + "MyBaseService.java.vm");
        listFacade.add(PREFIX_SERVICE + "MyServiceExecutor.java.vm");
        listFacade.add(PREFIX_POJO + "MyRequestDTO.java.vm");
        listFacade.add(PREFIX_POJO + "MyResponseDTO.java.vm");
        return map.set(ASSEMBLY, list).set(FACADE, listFacade);
    }

    @Override
    public List<String> initServiceTemplate() {
        List<String> list = new ArrayList<>(2);
        list.add(PREFIX_SERVICE + "MyBaseService.java.vm");
        list.add(PREFIX_SERVICE + "MyServiceExecutor.java.vm");
        return list;
    }

    @Override
    protected <T extends BaseInfo> String getDirectName(T info, String template, String type) {
        StringBuilder sb = new StringBuilder();
        ProjectInfo projectInfo = (ProjectInfo) info;
        String moduleName = projectInfo.getModuleName();
        String modulePrefix = projectInfo.getModulePrefix();
        String packageName = projectInfo.getGroupId();
        String packagePath = StringUtils.isEmpty(packageName) ? "" : packageName.replace(".", SE);

        sb
                .append(moduleName).append(SE).append(moduleName).append(HENG).append(template).append(SE)
                .append("src").append(SE).append(type).append(SE).append("java").append(SE).append(packagePath)
                .append(SE);
        if (!packagePath.contains(modulePrefix)) sb.append(modulePrefix).append(SE);
        sb.append(template).append(SE);
        return sb.toString();
    }

    @Override
    protected void generatorDirect(GeneratorInfo info, VelocityContext context, List<String> template) {
        ZipOutputStream zip = (ZipOutputStream) info.getOutputStream();
        Arrays.stream(ARRAY_DIRECT).forEach(t ->
                template.forEach(e -> {
                    try {
                        zip.putNextEntry(new ZipEntry(getDirectName(info.getInfo(), e, t)));
                        zip.closeEntry();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                })
        );
    }

    @Override
    protected <T extends BaseInfo> String getYmlName(T info, String template) {
        String name = getNameByTemplateName(template);
        StringBuilder sb = new StringBuilder();
        ProjectInfo projectInfo = (ProjectInfo) info;
        String moduleName = projectInfo.getModuleName();
        sb
                .append(moduleName).append(SE).append(moduleName).append(HENG).append(ASSEMBLY).append(SE).append("src").append(SE)
                .append("main").append(SE).append("resources").append(SE).append("config").append(SE);

        if (template.contains("log")) sb.append("log").append(SE);
        return sb.append(name).toString();

    }

    @Override
    protected void generatorYml(GeneratorInfo info, VelocityContext context, List<String> template) {
        ZipOutputStream zip = (ZipOutputStream) info.getOutputStream();
        template.forEach(e -> {
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(e, StandardCharsets.UTF_8.name());
            tpl.merge(context, sw);
            try {
                zip.putNextEntry(new ZipEntry(getYmlName(info.getInfo(), e)));
                IOUtils.write(sw.toString(), zip, Charsets.UTF_8.toString());
                IOUtils.closeQuietly(sw);
                zip.closeEntry();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        });
    }

    @Override
    protected <T extends BaseInfo> String getPomName(T info, String template) {
        StringBuilder sb = new StringBuilder();
        ProjectInfo projectInfo = (ProjectInfo) info;
        String moduleName = projectInfo.getModuleName();
        for (String s : arrayModule) {
            if (template.contains(s + XIE + "pom.xml.vm")) {
                return sb.append(moduleName).append(SE).append(moduleName).append(HENG).append(s).append(SE).append("pom.xml").toString();
            }
        }
        String prefix = template.replace(SUFFIX, "");
        String substring = prefix.substring(prefix.lastIndexOf(XIE) + 1);
        return sb.append(moduleName).append(SE).append(substring).toString();
    }

    @Override
    protected void generatorPom(GeneratorInfo info, VelocityContext context, List<String> template) {
        ZipOutputStream zip = (ZipOutputStream) info.getOutputStream();
        template.forEach(e -> {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(e, StandardCharsets.UTF_8.name());
            tpl.merge(context, sw);
            try {
                zip.putNextEntry(new ZipEntry(getPomName(info.getInfo(), e)));
                IOUtils.write(sw.toString(), zip, Charsets.UTF_8.toString());
                IOUtils.closeQuietly(sw);
                zip.closeEntry();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        });
    }


    @Override
    protected <T extends BaseInfo> String getApplicationName(T info, String template) {
        StringBuilder sb = new StringBuilder();
        ProjectInfo projectInfo = (ProjectInfo) info;
        String moduleNameUp = projectInfo.getModuleNameUp();
        String modulePrefix = projectInfo.getModulePrefix();
        String moduleName = projectInfo.getModuleName();
        String packageName = projectInfo.getGroupId();
        String packagePath = StringUtils.isEmpty(packageName) ? "" : packageName.replace(".", SE);
        sb
                .append(moduleName).append(SE).append(moduleName).append(HENG).append(ASSEMBLY).append(SE).append("src")
                .append(SE).append("main").append(SE).append("java").append(SE);
        sb.append(packagePath).append(SE);
        if (!packagePath.contains(modulePrefix)) sb.append(modulePrefix).append(SE);
        return sb.append(moduleNameUp).append("Application.java").toString();
    }

    @Override
    protected <T extends BaseInfo> String getModuleConfigName(T info, String template, String projectModuleName) {
        String name = getNameByTemplateName(template);
        name = CamelUtil.castUpper(name);
        StringBuilder sb = new StringBuilder();
        ProjectInfo projectInfo = (ProjectInfo) info;
        String moduleNameUp = projectInfo.getModuleNameUp();
        String modulePrefix = projectInfo.getModulePrefix();
        String moduleName = projectInfo.getModuleName();
        String packageName = projectInfo.getGroupId();
        String packagePath = StringUtils.isEmpty(packageName) ? "" : packageName.replace(".", SE);
        sb
                .append(moduleName).append(SE).append(moduleName).append(HENG).append(projectModuleName).append(SE).append("src")
                .append(SE).append("main").append(SE).append("java").append(SE);
        sb.append(packagePath).append(SE);
        if (!packagePath.contains(modulePrefix)) sb.append(modulePrefix).append(SE);
        sb.append(projectModuleName).append(SE);
        if (template.contains("config")) {
            sb.append("config").append(SE);
        }
        if (template.contains("application")) {
            sb.append(moduleNameUp);
        }
        if (template.contains(SERVICE)) {
            sb.append(SERVICE).append(SE);
        }
        if (template.contains(POJO)) {
            sb.append(POJO).append(SE).append("base").append(SE);
        }
        return sb.append(name).toString();
    }

    @Override
    protected void generatorApplication(GeneratorInfo info, VelocityContext context, List<String> template) {
        ZipOutputStream zip = (ZipOutputStream) info.getOutputStream();
        template.forEach(e -> {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(e, StandardCharsets.UTF_8.name());
            tpl.merge(context, sw);
            try {
                zip.putNextEntry(new ZipEntry(getApplicationName(info.getInfo(), e)));
                IOUtils.write(sw.toString(), zip, Charsets.UTF_8.toString());
                IOUtils.closeQuietly(sw);
                zip.closeEntry();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        });
    }

    @Override
    protected void generatorModuleConfig(GeneratorInfo info, VelocityContext context, Map<String, List<String>> templateMap) {
        ZipOutputStream zip = (ZipOutputStream) info.getOutputStream();
        templateMap.forEach((k, v) ->
                v.forEach(e -> {
                    //渲染模板
                    StringWriter sw = new StringWriter();
                    Template tpl = Velocity.getTemplate(e, StandardCharsets.UTF_8.name());
                    tpl.merge(context, sw);
                    try {
                        zip.putNextEntry(new ZipEntry(getModuleConfigName(info.getInfo(), e, k)));
                        IOUtils.write(sw.toString(), zip, Charsets.UTF_8.toString());
                        IOUtils.closeQuietly(sw);
                        zip.closeEntry();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                })
        );
    }

    @Override
    protected <T extends BaseInfo> String getMybatisConfigName(T info, String template) {
        String name = getNameByTemplateName(template);
        StringBuilder sb = new StringBuilder();
        ProjectInfo projectInfo = (ProjectInfo) info;
        String moduleName = projectInfo.getModuleName();
        sb
                .append(moduleName).append(SE).append(moduleName).append(HENG).append(PERSIST).append(SE).append("src")
                .append(SE).append("main").append(SE).append("resources");
        return sb.append(SE).append("mybatis").append(SE).append(name).toString();
    }


    @Override
    protected void generatorMybatisConfig(GeneratorInfo info, VelocityContext context, List<String> template) {
        ZipOutputStream zip = (ZipOutputStream) info.getOutputStream();
        template.forEach(e -> {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(e, StandardCharsets.UTF_8.name());
            tpl.merge(context, sw);
            try {
                zip.putNextEntry(new ZipEntry(getMybatisConfigName(info.getInfo(), e)));
                IOUtils.write(sw.toString(), zip, Charsets.UTF_8.toString());
                IOUtils.closeQuietly(sw);
                zip.closeEntry();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        });
    }


    @Override
    protected <T extends BaseInfo> Map<String, Object> paramMap(T info) {
        ProjectInfo projectInfo = (ProjectInfo) info;
        IMap<String, Object> map = new IMap<>(10);
        String packagePrefix = projectInfo.getGroupId().contains(projectInfo.getModulePrefix()) ? projectInfo.getGroupId()
                : projectInfo.getGroupId().concat(".").concat(projectInfo.getModulePrefix());

        return map
                // package info
                .set("project-groupId", projectInfo.getGroupId())
                .set("project-artifactId", projectInfo.getArtifactId())
                .set("project-version", projectInfo.getVersion())
                .set("package-name", projectInfo.getGroupId())
                .set("package-prefix", packagePrefix)
                //project name
                .set("module-prefix", projectInfo.getModulePrefix())
                .set("module-name", projectInfo.getModuleName())
                .set("module-name-up", projectInfo.getModuleNameUp())
                .set("module-name-lower", projectInfo.getModuleNameLow())
                //author info
                .set("author", "Stone")
                .set("date", LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME))
                // data source info
                .set("dataSource", projectInfo.getDataSource())
                .set("module-up", projectInfo.getModuleUp());
    }


}
